1   package org.apache.lucene.uninverting;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  import java.io.IOException;
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.ArrayList;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import org.apache.lucene.analysis.Analyzer;
28  import org.apache.lucene.analysis.MockAnalyzer;
29  import org.apache.lucene.codecs.Codec;
30  import org.apache.lucene.document.Document;
31  import org.apache.lucene.document.Field;
32  import org.apache.lucene.document.IntField;
33  import org.apache.lucene.document.LongField;
34  import org.apache.lucene.document.StringField;
35  import org.apache.lucene.index.LeafReader;
36  import org.apache.lucene.index.LeafReaderContext;
37  import org.apache.lucene.index.DirectoryReader;
38  import org.apache.lucene.index.DocValues;
39  import org.apache.lucene.index.IndexReader;
40  import org.apache.lucene.index.IndexWriter;
41  import org.apache.lucene.index.IndexWriterConfig;
42  import org.apache.lucene.index.MultiFields;
43  import org.apache.lucene.index.NumericDocValues;
44  import org.apache.lucene.index.RandomIndexWriter;
45  import org.apache.lucene.index.SlowCompositeReaderWrapper;
46  import org.apache.lucene.index.SortedSetDocValues;
47  import org.apache.lucene.index.Term;
48  import org.apache.lucene.index.Terms;
49  import org.apache.lucene.index.TermsEnum;
50  import org.apache.lucene.index.TermsEnum.SeekStatus;
51  import org.apache.lucene.store.Directory;
52  import org.apache.lucene.util.BytesRef;
53  import org.apache.lucene.util.LuceneTestCase;
54  import org.apache.lucene.util.NumericUtils;
55  import org.apache.lucene.util.StringHelper;
56  import org.apache.lucene.util.TestUtil;
57  
58  // TODO:
59  //   - test w/ del docs
60  //   - test prefix
61  //   - test w/ cutoff
62  //   - crank docs way up so we get some merging sometimes
63  
64  public class TestDocTermOrds extends LuceneTestCase {
65  
66    public void testEmptyIndex() throws IOException {
67      final Directory dir = newDirectory();
68      final IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())));
69      iw.close();
70      
71      final DirectoryReader ir = DirectoryReader.open(dir);
72      TestUtil.checkReader(ir);
73      
74      final LeafReader composite = SlowCompositeReaderWrapper.wrap(ir);
75      TestUtil.checkReader(composite);
76      
77      // check the leaves
78      // (normally there are none for an empty index, so this is really just future
79      // proofing in case that changes for some reason)
80      for (LeafReaderContext rc : ir.leaves()) {
81        final LeafReader r = rc.reader();
82        final DocTermOrds dto = new DocTermOrds(r, r.getLiveDocs(), "any_field");
83        assertNull("OrdTermsEnum should be null (leaf)", dto.getOrdTermsEnum(r));
84        assertEquals("iterator should be empty (leaf)", 0, dto.iterator(r).getValueCount());
85      }
86  
87      // check the composite 
88      final DocTermOrds dto = new DocTermOrds(composite, composite.getLiveDocs(), "any_field");
89      assertNull("OrdTermsEnum should be null (composite)", dto.getOrdTermsEnum(composite));
90      assertEquals("iterator should be empty (composite)", 0, dto.iterator(composite).getValueCount());
91  
92      ir.close();
93      dir.close();
94    }
95  
96    public void testSimple() throws Exception {
97      Directory dir = newDirectory();
98      final RandomIndexWriter w = new RandomIndexWriter(random(), dir, newIndexWriterConfig(new MockAnalyzer(random())).setMergePolicy(newLogMergePolicy()));
99      Document doc = new Document();
100     Field field = newTextField("field", "", Field.Store.NO);
101     doc.add(field);
102     field.setStringValue("a b c");
103     w.addDocument(doc);
104 
105     field.setStringValue("d e f");
106     w.addDocument(doc);
107 
108     field.setStringValue("a f");
109     w.addDocument(doc);
110     
111     final IndexReader r = w.getReader();
112     w.close();
113 
114     final LeafReader ar = SlowCompositeReaderWrapper.wrap(r);
115     TestUtil.checkReader(ar);
116     final DocTermOrds dto = new DocTermOrds(ar, ar.getLiveDocs(), "field");
117     SortedSetDocValues iter = dto.iterator(ar);
118     
119     iter.setDocument(0);
120     assertEquals(0, iter.nextOrd());
121     assertEquals(1, iter.nextOrd());
122     assertEquals(2, iter.nextOrd());
123     assertEquals(SortedSetDocValues.NO_MORE_ORDS, iter.nextOrd());
124     
125     iter.setDocument(1);
126     assertEquals(3, iter.nextOrd());
127     assertEquals(4, iter.nextOrd());
128     assertEquals(5, iter.nextOrd());
129     assertEquals(SortedSetDocValues.NO_MORE_ORDS, iter.nextOrd());
130 
131     iter.setDocument(2);
132     assertEquals(0, iter.nextOrd());
133     assertEquals(5, iter.nextOrd());
134     assertEquals(SortedSetDocValues.NO_MORE_ORDS, iter.nextOrd());
135 
136     r.close();
137     dir.close();
138   }
139 
140   public void testRandom() throws Exception {
141     Directory dir = newDirectory();
142 
143     final int NUM_TERMS = atLeast(20);
144     final Set<BytesRef> terms = new HashSet<>();
145     while(terms.size() < NUM_TERMS) {
146       final String s = TestUtil.randomRealisticUnicodeString(random());
147       //final String s = _TestUtil.randomSimpleString(random);
148       if (s.length() > 0) {
149         terms.add(new BytesRef(s));
150       }
151     }
152     final BytesRef[] termsArray = terms.toArray(new BytesRef[terms.size()]);
153     Arrays.sort(termsArray);
154     
155     final int NUM_DOCS = atLeast(100);
156 
157     IndexWriterConfig conf = newIndexWriterConfig(new MockAnalyzer(random()));
158 
159     // Sometimes swap in codec that impls ord():
160     if (random().nextInt(10) == 7) {
161       // Make sure terms index has ords:
162       Codec codec = TestUtil.alwaysPostingsFormat(TestUtil.getPostingsFormatWithOrds(random()));
163       conf.setCodec(codec);
164     }
165     
166     final RandomIndexWriter w = new RandomIndexWriter(random(), dir, conf);
167 
168     final int[][] idToOrds = new int[NUM_DOCS][];
169     final Set<Integer> ordsForDocSet = new HashSet<>();
170 
171     for(int id=0;id<NUM_DOCS;id++) {
172       Document doc = new Document();
173 
174       doc.add(new IntField("id", id, Field.Store.YES));
175       
176       final int termCount = TestUtil.nextInt(random(), 0, 20 * RANDOM_MULTIPLIER);
177       while(ordsForDocSet.size() < termCount) {
178         ordsForDocSet.add(random().nextInt(termsArray.length));
179       }
180       final int[] ordsForDoc = new int[termCount];
181       int upto = 0;
182       if (VERBOSE) {
183         System.out.println("TEST: doc id=" + id);
184       }
185       for(int ord : ordsForDocSet) {
186         ordsForDoc[upto++] = ord;
187         Field field = newStringField("field", termsArray[ord].utf8ToString(), Field.Store.NO);
188         if (VERBOSE) {
189           System.out.println("  f=" + termsArray[ord].utf8ToString());
190         }
191         doc.add(field);
192       }
193       ordsForDocSet.clear();
194       Arrays.sort(ordsForDoc);
195       idToOrds[id] = ordsForDoc;
196       w.addDocument(doc);
197     }
198     
199     final DirectoryReader r = w.getReader();
200     w.close();
201 
202     if (VERBOSE) {
203       System.out.println("TEST: reader=" + r);
204     }
205 
206     for(LeafReaderContext ctx : r.leaves()) {
207       if (VERBOSE) {
208         System.out.println("\nTEST: sub=" + ctx.reader());
209       }
210       verify(ctx.reader(), idToOrds, termsArray, null);
211     }
212 
213     // Also test top-level reader: its enum does not support
214     // ord, so this forces the OrdWrapper to run:
215     if (VERBOSE) {
216       System.out.println("TEST: top reader");
217     }
218     LeafReader slowR = SlowCompositeReaderWrapper.wrap(r);
219     TestUtil.checkReader(slowR);
220     verify(slowR, idToOrds, termsArray, null);
221 
222     FieldCache.DEFAULT.purgeByCacheKey(slowR.getCoreCacheKey());
223 
224     r.close();
225     dir.close();
226   }
227 
228   public void testRandomWithPrefix() throws Exception {
229     Directory dir = newDirectory();
230 
231     final Set<String> prefixes = new HashSet<>();
232     final int numPrefix = TestUtil.nextInt(random(), 2, 7);
233     if (VERBOSE) {
234       System.out.println("TEST: use " + numPrefix + " prefixes");
235     }
236     while(prefixes.size() < numPrefix) {
237       prefixes.add(TestUtil.randomRealisticUnicodeString(random()));
238       //prefixes.add(_TestUtil.randomSimpleString(random));
239     }
240     final String[] prefixesArray = prefixes.toArray(new String[prefixes.size()]);
241 
242     final int NUM_TERMS = atLeast(20);
243     final Set<BytesRef> terms = new HashSet<>();
244     while(terms.size() < NUM_TERMS) {
245       final String s = prefixesArray[random().nextInt(prefixesArray.length)] + TestUtil.randomRealisticUnicodeString(random());
246       //final String s = prefixesArray[random.nextInt(prefixesArray.length)] + _TestUtil.randomSimpleString(random);
247       if (s.length() > 0) {
248         terms.add(new BytesRef(s));
249       }
250     }
251     final BytesRef[] termsArray = terms.toArray(new BytesRef[terms.size()]);
252     Arrays.sort(termsArray);
253     
254     final int NUM_DOCS = atLeast(100);
255 
256     IndexWriterConfig conf = newIndexWriterConfig(new MockAnalyzer(random()));
257 
258     // Sometimes swap in codec that impls ord():
259     if (random().nextInt(10) == 7) {
260       Codec codec = TestUtil.alwaysPostingsFormat(TestUtil.getPostingsFormatWithOrds(random()));
261       conf.setCodec(codec);
262     }
263     
264     final RandomIndexWriter w = new RandomIndexWriter(random(), dir, conf);
265 
266     final int[][] idToOrds = new int[NUM_DOCS][];
267     final Set<Integer> ordsForDocSet = new HashSet<>();
268 
269     for(int id=0;id<NUM_DOCS;id++) {
270       Document doc = new Document();
271 
272       doc.add(new IntField("id", id, Field.Store.YES));
273       
274       final int termCount = TestUtil.nextInt(random(), 0, 20 * RANDOM_MULTIPLIER);
275       while(ordsForDocSet.size() < termCount) {
276         ordsForDocSet.add(random().nextInt(termsArray.length));
277       }
278       final int[] ordsForDoc = new int[termCount];
279       int upto = 0;
280       if (VERBOSE) {
281         System.out.println("TEST: doc id=" + id);
282       }
283       for(int ord : ordsForDocSet) {
284         ordsForDoc[upto++] = ord;
285         Field field = newStringField("field", termsArray[ord].utf8ToString(), Field.Store.NO);
286         if (VERBOSE) {
287           System.out.println("  f=" + termsArray[ord].utf8ToString());
288         }
289         doc.add(field);
290       }
291       ordsForDocSet.clear();
292       Arrays.sort(ordsForDoc);
293       idToOrds[id] = ordsForDoc;
294       w.addDocument(doc);
295     }
296     
297     final DirectoryReader r = w.getReader();
298     w.close();
299 
300     if (VERBOSE) {
301       System.out.println("TEST: reader=" + r);
302     }
303     
304     LeafReader slowR = SlowCompositeReaderWrapper.wrap(r);
305     TestUtil.checkReader(slowR);
306     for(String prefix : prefixesArray) {
307 
308       final BytesRef prefixRef = prefix == null ? null : new BytesRef(prefix);
309 
310       final int[][] idToOrdsPrefix = new int[NUM_DOCS][];
311       for(int id=0;id<NUM_DOCS;id++) {
312         final int[] docOrds = idToOrds[id];
313         final List<Integer> newOrds = new ArrayList<>();
314         for(int ord : idToOrds[id]) {
315           if (StringHelper.startsWith(termsArray[ord], prefixRef)) {
316             newOrds.add(ord);
317           }
318         }
319         final int[] newOrdsArray = new int[newOrds.size()];
320         int upto = 0;
321         for(int ord : newOrds) {
322           newOrdsArray[upto++] = ord;
323         }
324         idToOrdsPrefix[id] = newOrdsArray;
325       }
326 
327       for(LeafReaderContext ctx : r.leaves()) {
328         if (VERBOSE) {
329           System.out.println("\nTEST: sub=" + ctx.reader());
330         }
331         verify(ctx.reader(), idToOrdsPrefix, termsArray, prefixRef);
332       }
333 
334       // Also test top-level reader: its enum does not support
335       // ord, so this forces the OrdWrapper to run:
336       if (VERBOSE) {
337         System.out.println("TEST: top reader");
338       }
339       verify(slowR, idToOrdsPrefix, termsArray, prefixRef);
340     }
341 
342     FieldCache.DEFAULT.purgeByCacheKey(slowR.getCoreCacheKey());
343 
344     r.close();
345     dir.close();
346   }
347 
348   private void verify(LeafReader r, int[][] idToOrds, BytesRef[] termsArray, BytesRef prefixRef) throws Exception {
349 
350     final DocTermOrds dto = new DocTermOrds(r, r.getLiveDocs(),
351                                             "field",
352                                             prefixRef,
353                                             Integer.MAX_VALUE,
354                                             TestUtil.nextInt(random(), 2, 10));
355                                             
356 
357     final NumericDocValues docIDToID = FieldCache.DEFAULT.getNumerics(r, "id", FieldCache.NUMERIC_UTILS_INT_PARSER, false);
358     /*
359       for(int docID=0;docID<subR.maxDoc();docID++) {
360       System.out.println("  docID=" + docID + " id=" + docIDToID[docID]);
361       }
362     */
363 
364     if (VERBOSE) {
365       System.out.println("TEST: verify prefix=" + (prefixRef==null ? "null" : prefixRef.utf8ToString()));
366       System.out.println("TEST: all TERMS:");
367       TermsEnum allTE = MultiFields.getTerms(r, "field").iterator();
368       int ord = 0;
369       while(allTE.next() != null) {
370         System.out.println("  ord=" + (ord++) + " term=" + allTE.term().utf8ToString());
371       }
372     }
373 
374     //final TermsEnum te = subR.fields().terms("field").iterator();
375     final TermsEnum te = dto.getOrdTermsEnum(r);
376     if (dto.numTerms() == 0) {
377       if (prefixRef == null) {
378         assertNull(MultiFields.getTerms(r, "field"));
379       } else {
380         Terms terms = MultiFields.getTerms(r, "field");
381         if (terms != null) {
382           TermsEnum termsEnum = terms.iterator();
383           TermsEnum.SeekStatus result = termsEnum.seekCeil(prefixRef);
384           if (result != TermsEnum.SeekStatus.END) {
385             assertFalse("term=" + termsEnum.term().utf8ToString() + " matches prefix=" + prefixRef.utf8ToString(), StringHelper.startsWith(termsEnum.term(), prefixRef));
386           } else {
387             // ok
388           }
389         } else {
390           // ok
391         }
392       }
393       return;
394     }
395 
396     if (VERBOSE) {
397       System.out.println("TEST: TERMS:");
398       te.seekExact(0);
399       while(true) {
400         System.out.println("  ord=" + te.ord() + " term=" + te.term().utf8ToString());
401         if (te.next() == null) {
402           break;
403         }
404       }
405     }
406 
407     SortedSetDocValues iter = dto.iterator(r);
408     for(int docID=0;docID<r.maxDoc();docID++) {
409       if (VERBOSE) {
410         System.out.println("TEST: docID=" + docID + " of " + r.maxDoc() + " (id=" + docIDToID.get(docID) + ")");
411       }
412       iter.setDocument(docID);
413       final int[] answers = idToOrds[(int) docIDToID.get(docID)];
414       int upto = 0;
415       long ord;
416       while ((ord = iter.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) {
417         te.seekExact(ord);
418         final BytesRef expected = termsArray[answers[upto++]];
419         if (VERBOSE) {
420           System.out.println("  exp=" + expected.utf8ToString() + " actual=" + te.term().utf8ToString());
421         }
422         assertEquals("expected=" + expected.utf8ToString() + " actual=" + te.term().utf8ToString() + " ord=" + ord, expected, te.term());
423       }
424       assertEquals(answers.length, upto);
425     }
426   }
427   
428   public void testBackToTheFuture() throws Exception {
429     Directory dir = newDirectory();
430     IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(null));
431     
432     Document doc = new Document();
433     doc.add(newStringField("foo", "bar", Field.Store.NO));
434     iw.addDocument(doc);
435     
436     doc = new Document();
437     doc.add(newStringField("foo", "baz", Field.Store.NO));
438     // we need a second value for a doc, or we don't actually test DocTermOrds!
439     doc.add(newStringField("foo", "car", Field.Store.NO));
440     iw.addDocument(doc);
441     
442     DirectoryReader r1 = DirectoryReader.open(iw, true);
443     
444     iw.deleteDocuments(new Term("foo", "baz"));
445     DirectoryReader r2 = DirectoryReader.open(iw, true);
446     
447     FieldCache.DEFAULT.getDocTermOrds(getOnlySegmentReader(r2), "foo", null);
448     
449     SortedSetDocValues v = FieldCache.DEFAULT.getDocTermOrds(getOnlySegmentReader(r1), "foo", null);
450     assertEquals(3, v.getValueCount());
451     v.setDocument(1);
452     assertEquals(1, v.nextOrd());
453     
454     iw.close();
455     r1.close();
456     r2.close();
457     dir.close();
458   }
459   
460   public void testNumericEncoded32() throws IOException {
461     Directory dir = newDirectory();
462     IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(null));
463     
464     Document doc = new Document();
465     doc.add(new IntField("foo", 5, Field.Store.NO));
466     iw.addDocument(doc);
467     
468     doc = new Document();
469     doc.add(new IntField("foo", 5, Field.Store.NO));
470     doc.add(new IntField("foo", -3, Field.Store.NO));
471     iw.addDocument(doc);
472     
473     iw.forceMerge(1);
474     iw.close();
475     
476     DirectoryReader ir = DirectoryReader.open(dir);
477     LeafReader ar = getOnlySegmentReader(ir);
478     
479     SortedSetDocValues v = FieldCache.DEFAULT.getDocTermOrds(ar, "foo", FieldCache.INT32_TERM_PREFIX);
480     assertEquals(2, v.getValueCount());
481     
482     v.setDocument(0);
483     assertEquals(1, v.nextOrd());
484     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
485     
486     v.setDocument(1);
487     assertEquals(0, v.nextOrd());
488     assertEquals(1, v.nextOrd());
489     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
490     
491     BytesRef value = v.lookupOrd(0);
492     assertEquals(-3, NumericUtils.prefixCodedToInt(value));
493     
494     value = v.lookupOrd(1);
495     assertEquals(5, NumericUtils.prefixCodedToInt(value));
496     
497     ir.close();
498     dir.close();
499   }
500   
501   public void testNumericEncoded64() throws IOException {
502     Directory dir = newDirectory();
503     IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(null));
504     
505     Document doc = new Document();
506     doc.add(new LongField("foo", 5, Field.Store.NO));
507     iw.addDocument(doc);
508     
509     doc = new Document();
510     doc.add(new LongField("foo", 5, Field.Store.NO));
511     doc.add(new LongField("foo", -3, Field.Store.NO));
512     iw.addDocument(doc);
513     
514     iw.forceMerge(1);
515     iw.close();
516     
517     DirectoryReader ir = DirectoryReader.open(dir);
518     LeafReader ar = getOnlySegmentReader(ir);
519     
520     SortedSetDocValues v = FieldCache.DEFAULT.getDocTermOrds(ar, "foo", FieldCache.INT64_TERM_PREFIX);
521     assertEquals(2, v.getValueCount());
522     
523     v.setDocument(0);
524     assertEquals(1, v.nextOrd());
525     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
526     
527     v.setDocument(1);
528     assertEquals(0, v.nextOrd());
529     assertEquals(1, v.nextOrd());
530     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
531     
532     BytesRef value = v.lookupOrd(0);
533     assertEquals(-3, NumericUtils.prefixCodedToLong(value));
534     
535     value = v.lookupOrd(1);
536     assertEquals(5, NumericUtils.prefixCodedToLong(value));
537     
538     ir.close();
539     dir.close();
540   }
541   
542   public void testSortedTermsEnum() throws IOException {
543     Directory directory = newDirectory();
544     Analyzer analyzer = new MockAnalyzer(random());
545     IndexWriterConfig iwconfig = newIndexWriterConfig(analyzer);
546     iwconfig.setMergePolicy(newLogMergePolicy());
547     RandomIndexWriter iwriter = new RandomIndexWriter(random(), directory, iwconfig);
548     
549     Document doc = new Document();
550     doc.add(new StringField("field", "hello", Field.Store.NO));
551     iwriter.addDocument(doc);
552     
553     doc = new Document();
554     doc.add(new StringField("field", "world", Field.Store.NO));
555     // we need a second value for a doc, or we don't actually test DocTermOrds!
556     doc.add(new StringField("field", "hello", Field.Store.NO));
557     iwriter.addDocument(doc);
558 
559     doc = new Document();
560     doc.add(new StringField("field", "beer", Field.Store.NO));
561     iwriter.addDocument(doc);
562     iwriter.forceMerge(1);
563     
564     DirectoryReader ireader = iwriter.getReader();
565     iwriter.close();
566 
567     LeafReader ar = getOnlySegmentReader(ireader);
568     SortedSetDocValues dv = FieldCache.DEFAULT.getDocTermOrds(ar, "field", null);
569     assertEquals(3, dv.getValueCount());
570     
571     TermsEnum termsEnum = dv.termsEnum();
572     
573     // next()
574     assertEquals("beer", termsEnum.next().utf8ToString());
575     assertEquals(0, termsEnum.ord());
576     assertEquals("hello", termsEnum.next().utf8ToString());
577     assertEquals(1, termsEnum.ord());
578     assertEquals("world", termsEnum.next().utf8ToString());
579     assertEquals(2, termsEnum.ord());
580     
581     // seekCeil()
582     assertEquals(SeekStatus.NOT_FOUND, termsEnum.seekCeil(new BytesRef("ha!")));
583     assertEquals("hello", termsEnum.term().utf8ToString());
584     assertEquals(1, termsEnum.ord());
585     assertEquals(SeekStatus.FOUND, termsEnum.seekCeil(new BytesRef("beer")));
586     assertEquals("beer", termsEnum.term().utf8ToString());
587     assertEquals(0, termsEnum.ord());
588     assertEquals(SeekStatus.END, termsEnum.seekCeil(new BytesRef("zzz")));
589     
590     // seekExact()
591     assertTrue(termsEnum.seekExact(new BytesRef("beer")));
592     assertEquals("beer", termsEnum.term().utf8ToString());
593     assertEquals(0, termsEnum.ord());
594     assertTrue(termsEnum.seekExact(new BytesRef("hello")));
595     assertEquals("hello", termsEnum.term().utf8ToString());
596     assertEquals(1, termsEnum.ord());
597     assertTrue(termsEnum.seekExact(new BytesRef("world")));
598     assertEquals("world", termsEnum.term().utf8ToString());
599     assertEquals(2, termsEnum.ord());
600     assertFalse(termsEnum.seekExact(new BytesRef("bogus")));
601     
602     // seek(ord)
603     termsEnum.seekExact(0);
604     assertEquals("beer", termsEnum.term().utf8ToString());
605     assertEquals(0, termsEnum.ord());
606     termsEnum.seekExact(1);
607     assertEquals("hello", termsEnum.term().utf8ToString());
608     assertEquals(1, termsEnum.ord());
609     termsEnum.seekExact(2);
610     assertEquals("world", termsEnum.term().utf8ToString());
611     assertEquals(2, termsEnum.ord());
612     
613     // lookupTerm(BytesRef) 
614     assertEquals(-1, dv.lookupTerm(new BytesRef("apple")));
615     assertEquals(0, dv.lookupTerm(new BytesRef("beer")));
616     assertEquals(-2, dv.lookupTerm(new BytesRef("car")));
617     assertEquals(1, dv.lookupTerm(new BytesRef("hello")));
618     assertEquals(-3, dv.lookupTerm(new BytesRef("matter")));
619     assertEquals(2, dv.lookupTerm(new BytesRef("world")));
620     assertEquals(-4, dv.lookupTerm(new BytesRef("zany")));
621 
622     ireader.close();
623     directory.close();
624   }
625   
626   public void testActuallySingleValued() throws IOException {
627     Directory dir = newDirectory();
628     IndexWriterConfig iwconfig =  newIndexWriterConfig(null);
629     iwconfig.setMergePolicy(newLogMergePolicy());
630     IndexWriter iw = new IndexWriter(dir, iwconfig);
631     
632     Document doc = new Document();
633     doc.add(new StringField("foo", "bar", Field.Store.NO));
634     iw.addDocument(doc);
635     
636     doc = new Document();
637     doc.add(new StringField("foo", "baz", Field.Store.NO));
638     iw.addDocument(doc);
639     
640     doc = new Document();
641     iw.addDocument(doc);
642     
643     doc = new Document();
644     doc.add(new StringField("foo", "baz", Field.Store.NO));
645     doc.add(new StringField("foo", "baz", Field.Store.NO));
646     iw.addDocument(doc);
647     
648     iw.forceMerge(1);
649     iw.close();
650     
651     DirectoryReader ir = DirectoryReader.open(dir);
652     LeafReader ar = getOnlySegmentReader(ir);
653     
654     SortedSetDocValues v = FieldCache.DEFAULT.getDocTermOrds(ar, "foo", null);
655     assertNotNull(DocValues.unwrapSingleton(v)); // actually a single-valued field
656     assertEquals(2, v.getValueCount());
657     
658     v.setDocument(0);
659     assertEquals(0, v.nextOrd());
660     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
661     
662     v.setDocument(1);
663     assertEquals(1, v.nextOrd());
664     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
665     
666     v.setDocument(2);
667     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
668     
669     v.setDocument(3);
670     assertEquals(1, v.nextOrd());
671     assertEquals(SortedSetDocValues.NO_MORE_ORDS, v.nextOrd());
672     
673     BytesRef value = v.lookupOrd(0);
674     assertEquals("bar", value.utf8ToString());
675     
676     value = v.lookupOrd(1);
677     assertEquals("baz", value.utf8ToString());
678     
679     ir.close();
680     dir.close();
681   }
682 }